/*********************************************************************************
 *      Copyright:  (C) 2023 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  keypad.c
 *    Description:  This file used to test GPIO button driver builtin Linux kernel.
 *                 
 *        Version:  1.0.0(2023年05月20日)
 *         Author:  Ling Yun <lingyun@email.com>
 *      ChangeLog:  1, Release initial version on "2023年05月20日 14时58分43秒"
 *                 
 ********************************************************************************/

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <libgen.h>
#include <getopt.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <linux/input.h>
#include <linux/kd.h>
#include <linux/keyboard.h>

#if 0 /*  Just for comment here, Reference to linux-3.3/include/linux/input.h */
struct input_event 
{
	struct timeval time;
	__u16 type;  /*  0x00:EV_SYN 0x01:EV_KEY 0x04:EV_MSC 0x11:EV_LED*/
	__u16 code;  /*  key value, which key */
	__s32 value; /*  1: Pressed  0:Not pressed  2:Always Pressed */
};  
#endif

#define EV_RELEASED	0
#define EV_PRESSED	1

#define	BUTTON_CNT	10

/*  在C语言编程中，函数应该先定义再使用，如果函数的定义在函数调用后面，应该前向声明。 */
void usage(char *name);

void display_button_event(struct input_event *ev, int cnt);

int main(int argc, char **argv)
{
	char	 	*kbd_dev = "/dev/input/event1";  //默认监听按键设备；
	char		kbd_name[256] = "Unknown"; //用于保存获取到的设备名称
	int			kbd_fd = -1;  //open()打开文件的文件描述符 
	int   	    rv=0;  // 函数返回值，默认返回0；
	int         opt;    // getopt_long 解析命令行参数返回值；
	int         size = sizeof (struct input_event);
	fd_set      rds; //用于监听的事件的集合

	struct input_event    ev[BUTTON_CNT]; 

	/*  getopt_long参数函数第四个参数的定义，二维数组，每个成员由四个元素组成  */
	struct option long_options[] = {  
		/*  { 参数名称，是否带参数，flags指针(NULL时将val的数值从getopt_long的返回值返回出去),
		 *  函数找到该选项时的返回值(字符)}*/
		{"device", required_argument, NULL, 'd'},
		{"help", no_argument, NULL, 'h'},
		{NULL, 0, NULL, 0}
	};

	//获取命令行参数的解析返回值
	while((opt = getopt_long(argc,argv,"d:h",long_options,NULL)) != -1)
	{
		switch(opt)
		{
			case'd':
				kbd_dev = optarg;
				break;

			case'h':
				usage(argv[0]);
				return 0;

			default:
				break;
		}
	}

	if(NULL == kbd_dev)
	{
		    /*  命令行argv[0]是输入的命令，如 ./keypad */
		usage(argv[0]); 
		return -1;
	}

	/* 获取uid 建议以root权限确保正常运行 */
	if((getuid ())!= 0)
		printf("You are not root! This may not work...\n");

	/*  打开按键对应的设备节点，如果错误则返回负数 */
	if ((kbd_fd = open(kbd_dev, O_RDONLY)) < 0)
	{
		printf("Open %s failure: %s", kbd_dev, strerror(errno));
		return -1;
	}

	/*  使用ioctl获取 /dev/input/event*对应的设备名字 */
	ioctl (kbd_fd, EVIOCGNAME (sizeof (kbd_name)), kbd_name);
	printf ("Monitor input device %s (%s) event on poll mode:\n", kbd_dev, kbd_name);

	/* 循环使用select()多路复用监听按键事件 */
	while(1)
	{
		FD_ZERO(&rds); //清空select()的读事件集合
		FD_SET(kbd_fd,&rds);//将按键设备的文件描述符加入到读事件集合中

		 /*  使用select开启监听并等待多个描述符发生变化，第一个参数最大描述符+1，
		  *   2、3、4参数分别是要监听读、写、异常三个事件的文军描述符集合；
		  *  最后一个参数是超时时间(NULL-->永不超时，会一直阻塞住)
          如果按键没有按下，则程序一直阻塞在这里。一旦按键按下，则按键设备有数据可读，此时函数将返回。
		*/
		rv = select(kbd_fd + 1,&rds,NULL,NULL,NULL );
		if(rv < 0)
		{
			printf("Select() system call failure:%s\n",strerror(errno));
			goto CleanUp;
		}
		else if (FD_ISSET(kbd_fd,&rds))//是按键设备发生了事件
		{
			//read读取input设备的数据包,数据包为input_event结构体类型
			if((rv = read(kbd_fd,ev,size*BUTTON_CNT )) < size)
			{
				printf("Reading data from kbd_fd failure:%s\n",strerror(errno));
				break;
			}
			else
			{
				display_button_event(ev,rv/size);
			}
		}
	}

CleanUp:
	close(kbd_fd);

	return 0;
}

/*  该函数用来打印程序的使用方法  */
void usage(char *name)
{
	char *progname = NULL;
	char *ptr = NULL;

	/*  字符串拷贝函数，该函数内部将调用malloc()来动态分配内存，然后将$name
	 *  字符串内容拷贝到malloc分配的内存中，这样使用完之后需要free释放内存. */
	ptr = strdup(name);
	progname = basename(ptr);//去除该可执行文件的路径名，获取自身名称keypad

	printf("Usage: %s [-p] -d <device>\n", progname);
	printf(" -d[device  ] button device name\n");
	printf(" -p[poll    ] Use poll mode, or default use infinit loop.\n");
	printf(" -h[help    ] Display this help information\n"); 

	free(ptr);  //和strdup对应，释放该内存
	return;
}

/*  该函数用来解析按键设备上报的数据，并答应按键按下的相关信息  */
void display_button_event(struct input_event *ev, int cnt)
{
	int i;
	static struct timeval pressed_time;  //该变量用来存放按键按下的时间，注意static的使用。
	struct timeval        duration_time; //该变量用来存放按键按下持续时间 

	for(i=0;i<cnt;i++)
	{
		/*  当上报的时间type为EV_KEY时候并且，value值为1或0 (1为按下，0为释放)  */
		if(EV_KEY==ev[i].type && EV_PRESSED==ev[i].value)
		{
			pressed_time = ev[i].time;
			printf("Keypad[%d] pressed time: %ld.%ld\n", 
				 	ev[i].code, pressed_time.tv_sec, pressed_time.tv_usec);
		}
		if(EV_KEY==ev[i].type && EV_RELEASED==ev[i].value)
		{
			/*  计算时间差函数 将第一个参数减去第二个参数的值的结果 放到第三个参数之中 */
			timersub(&ev[i].time, &pressed_time, &duration_time);
			printf("keypad[%d] released time: %ld.%ld\n", 
					ev[i].code, ev[i].time.tv_sec, ev[i].time.tv_usec);
			printf("keypad[%d] duration time: %ld.%ld\n", 
					ev[i].code, duration_time.tv_sec, duration_time.tv_usec);
		}
	}
}


